import nodes.*;

public class EI implements Comparable {
	private IFwrapper addw, subw, mulw, ifw, gtw;
	private IFwrapper[] funcs;
	private IScoreFunc scorefunc;
	private int[][] map;
	private INode expTree;
	
	public EI() {
		addw = aritw("+");
		subw = aritw("-");
		mulw = aritw("*");
		ifw  = iffunc();
		gtw  = isGreater();
		funcs = new IFwrapper[]{addw, subw, mulw, ifw, gtw};
//		expTree = makeRandomTree(2); //delete this
	}
	//evolutionary methods
	public EI copy() {
		EI copy = new EI();
		copy.setTree(getTree().copy());
		copy.setMap(map);
		copy.setScorefunc(scorefunc);
		return copy;
	}
	public EI crossover(EI ei2, double probswap) {
		EI baby = new EI();
		baby.setTree(crossover(getTree(), ei2.getTree(), probswap, true));
		baby.setMap(map);
		baby.setScorefunc(scorefunc);
		return baby;
	}
	public void mutate(int pc, double probchange) {
		setTree(mutate(getTree(), pc, probchange));
	}
	private INode crossover(INode t1, INode t2, double probswap, boolean top) {
		if(Math.random() < probswap && !top) return t2.copy();
		else {
			if(t1 instanceof IParentNode && t2 instanceof IParentNode) {
				//System.out.println("HEST");
				IParentNode result = (IParentNode) t1.copy();
				IParentNode t2n = (IParentNode) t2;
				INode[] newchildren = new INode[result.getChildren().length];
				for(int i = 0; i < newchildren.length; i++) {
				//	System.out.println("InnerHEST");
					newchildren[i] = crossover(result.getChildren()[i], 
									t2n.getChildren()[(int) (Math.random()*t2n.getChildren().length)],
									probswap,
									false);
				}
				result.setChildren(newchildren);
				return result;
			}
			else return t1.copy();
		}
	}
	private INode mutate(INode tree, int pc, double probchange) {
		if(Math.random() < probchange)	return makeRandomTree(pc);
		else {
			if(tree instanceof IParentNode) {
				IParentNode result = (IParentNode) tree.copy();
				INode[] newchildren = new INode[result.getChildren().length];
				for(int i = 0; i < newchildren.length; i++)
					newchildren[i] = mutate(result.getChildren()[i], pc, probchange);
				result.setChildren(newchildren);
				return result;
			}
			else return tree.copy();
		}
	}

	//setters
	public void setMap(int[][] map) {
		this.map = map;
	}
	public void setTree(INode tree) {
		expTree = tree;
	}
	public void setScorefunc(IScoreFunc func) {
		scorefunc = func;
	}
	
	public void display() {
		expTree.display(0);
	}
	public INode makeRandomTree(int pc) {
		return makeRandomTree(pc, 4, 0.5, 0.6);
	}
	public INode makeRandomTree(int pc, int maxdepth, double fpr, double ppr) {
		if(Math.random() < fpr && maxdepth > 0) {
			IFwrapper f = funcs[(int)(Math.random() * funcs.length)];
			INode[] children = new INode[f.paramCount()];
			for(int i = 0; i < f.paramCount(); i++)
				children[i] = makeRandomTree(pc, maxdepth - 1, fpr, ppr);
			return node(f, children);
		}
		else {
			if(Math.random() < ppr)	return paramNode((int)(Math.random()*pc));
			else					return constNode((int)(Math.random()*10));
		}
	}
	//node constructors and functions wrappers
	public INode getTree() {
		return expTree;
	}
	
	private IParentNode node(final IFwrapper fw, final INode[] children) {
		return new IParentNode() {
			private INode[] innerchildren = children;
			public int evaluate(int[] inp) {
				int[] params = new int[fw.paramCount()];
				for(int i = 0; i < params.length; i++)
					params[i] = innerchildren[i].evaluate(inp);
				return fw.function(params);
			}
			public INode[] getChildren() {
				return innerchildren;
			}
			public void setChildren(INode[] newchildren) {
				innerchildren = newchildren;
			}
			public INode copy() {
				INode[] clonedChildren = new INode[innerchildren.length];
				for(int i = 0; i < clonedChildren.length; i++)
					clonedChildren[i] = innerchildren[i].copy();
				return node(fw, clonedChildren);
			}
			public String toString() {
				return fw.toString();
			}
			public void display(int lvl) {
				System.out.println(indent(lvl) + toString());
				for(int i = 0; i < innerchildren.length; i++)
					innerchildren[i].display(lvl + 1);
			}
		};
	}
	private INode constNode(final int value) {
		return new INode() {
			public int evaluate(int[] inp) {
				return value;
			}
			public INode copy() {
				return constNode(value);
			}
			public String toString() {
				return "" + value;
			}
			public void display(int lvl) {
				System.out.println(indent(lvl) + toString());
			}
		};
	}
	private INode paramNode(final int idx) {
		return new INode() {
			public int evaluate(int[] inp) {
				return inp[idx];
			}
			public INode copy() {
				return paramNode(idx);
			}
			public String toString() {
				return "p" + idx;
			}
			public void display(int lvl) {
				System.out.println(indent(lvl) + toString());				
			}
		};
	}
	private IFwrapper iffunc() {
		return new IFwrapper() {
			public int paramCount() {
				return 3;
			}
			public int function(int[] params) {
				if(params[0] > 0)	return params[1];
				else				return params[2];
			}
			public String toString() {
				return "if";
			}
		};
	}
	private IFwrapper isGreater() {
		return new IFwrapper() {
			public int paramCount() {
				return 2;
			}
			public int function(int[] params) {
				if(params[0] > params[1])	return 1;
				else						return 0;
			}
			public String toString() {
				return "isGreater";
			}
		};
	}
	private IFwrapper aritw(final String type) {
		return new IFwrapper() {
			public int paramCount() {
				return 2;
			}
			public int function(int[] params) {
				if(type.equals("*"))		return params[0] * params[1];
				if(type.equals("+")) 		return params[0] + params[1];
				/*if(type.equals("-"))*/	return params[0] - params[1];
			}
			public String toString() {
				if(type.equals("*"))		return "multiply";
				if(type.equals("+")) 		return "add";
				/*if(type.equals("-"))*/	return "substract";
				
			}
		};
	}
	//secondary functions.. primary display syntax etc.
	public int score() {
		return scorefunc.function(this, map);
	}
	public int compareTo(Object other) {
		EI ei2 = (EI) other;
		return score() - ei2.score();
	}
	private String indent(int lvl) {
		String ind = "";
		for(int i = 0; i < lvl; i++)
			ind += " ";
		return ind;
	}
}